package com.newfiber.business.service.impl;

import static cn.hutool.core.date.DatePattern.CHINESE_DATE_PATTERN;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MINUTE_PATTERN;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_PATTERN;
import static com.newfiber.business.constant.BusinessConstants.PATROL_LOG_EXPORT_FILE_NAME;
import static com.newfiber.business.constant.BusinessConstants.PATROL_LOG_EXPORT_TEMPLATE;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.newfiber.business.api.RemoteAddressService;
import com.newfiber.business.api.domain.AddressResult;
import com.newfiber.business.domain.PatrolCase;
import com.newfiber.business.domain.PatrolLog;
import com.newfiber.business.domain.PatrolSection;
import com.newfiber.business.domain.PatrolTask;
import com.newfiber.business.domain.request.patrolCase.PatrolCaseQueryRequest;
import com.newfiber.business.domain.request.patrolLog.PatrolLogExportRequest;
import com.newfiber.business.domain.request.patrolLog.PatrolLogQueryRequest;
import com.newfiber.business.domain.request.patrolLog.PatrolLogSaveRequest;
import com.newfiber.business.domain.request.patrolLog.PatrolLogSaveTempRequest;
import com.newfiber.business.domain.request.patrolLog.PatrolLogSubmitTempRequest;
import com.newfiber.business.domain.request.patrolLog.PatrolLogUpdateRequest;
import com.newfiber.business.domain.request.patrolPath.PatrolPathSaveRequest;
import com.newfiber.business.domain.request.patrolTask.PatrolTaskBeginRequest;
import com.newfiber.business.domain.request.patrolTask.PatrolTaskFinishRequest;
import com.newfiber.business.enums.EPatrolConfig;
import com.newfiber.business.enums.EPatrolLogStatus;
import com.newfiber.business.enums.EPatrolLogSubmitType;
import com.newfiber.business.enums.EPatrolRedisKey;
import com.newfiber.business.enums.EPatrolSectionType;
import com.newfiber.business.mapper.PatrolCaseMapper;
import com.newfiber.business.mapper.PatrolLogMapper;
import com.newfiber.business.service.IPatrolLogService;
import com.newfiber.business.service.IPatrolPunchService;
import com.newfiber.common.core.exception.ServiceException;
import com.newfiber.common.core.geometry.GeometryUtils;
import com.newfiber.common.core.web.domain.BaseEntity;
import com.newfiber.common.core.web.domain.Result;
import com.newfiber.common.core.web.service.BaseServiceImpl;
import com.newfiber.common.redis.service.RedisService;
import com.newfiber.common.security.utils.ConfigUtils;
import com.newfiber.system.api.RemoteFileService;
import com.newfiber.system.api.domain.SysFile;
import com.newfiber.utils.OfficeUtil;
import com.newfiber.utils.WordBaseService;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Polygon;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 巡查日志Service业务层处理
 *
 * @author X.K
 * @date 2023-02-17
 */
@Slf4j
@Service
public class PatrolLogServiceImpl extends BaseServiceImpl<PatrolLogMapper, PatrolLog> implements IPatrolLogService {

    @Resource
    private PatrolLogMapper patrolLogMapper;

    @Resource
    private PatrolCaseMapper patrolCaseMapper;

    @Resource
    private RedisService redisService;

    @Resource
    private RemoteAddressService remoteAddressService;

    @Resource
    private IPatrolPunchService patrolPunchService;

    @Resource
    private RemoteFileService remoteFileService;


    @Override
    public long insert(PatrolLogSaveRequest request) {
        PatrolLog patrolLog = new PatrolLog();
        BeanUtils.copyProperties(request, patrolLog);
        patrolLog.setPicture(JSONObject.toJSONString((request.getPictureListData())));
        save(patrolLog);
        return Optional.of(patrolLog).map(BaseEntity::getId).orElse(0L);
    }

    @Override
    public long insertTemp(PatrolLogSaveTempRequest request) {
        PatrolLog tempPatrolLog = selectTempByUserId(request.getLogUserId());
        if (null != tempPatrolLog) {
            throw new ServiceException("已存在临时巡查任务，无法重复添加");
        }

        PatrolLog patrolLog = new PatrolLog();
        BeanUtils.copyProperties(request, patrolLog);
        patrolLog.setNumber(generatorNumber("PL", NumberFormat.DateSerialNumber));
        patrolLog.setStartDatetime(new Date());
        patrolLog.setStatus(EPatrolLogStatus.Temp.getCode());
        save(patrolLog);
        return Optional.of(patrolLog).map(BaseEntity::getId).orElse(0L);
    }

    @Override
    public boolean insertBatch(List<PatrolTask> patrolTasks) {
        List<PatrolLog> patrolLogList = new ArrayList<>();
        for (PatrolTask patrolTask : patrolTasks) {
            PatrolLog patrolLog = new PatrolLog();
            BeanUtils.copyProperties(patrolTask, patrolLog);
            patrolLog.setPatrolTaskId(patrolTask.getId());
            patrolLog.setLogUserId(patrolTask.getTaskUserId());
            patrolLog.setStatus(EPatrolLogStatus.Draft.getCode());
            patrolLog.setPatrolPath(null);
            patrolLogList.add(patrolLog);
        }
        return saveBatch(patrolLogList, patrolLogList.size());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(String ids) {
        return deleteLogic(ids);
    }

    @Override
    public void updateCanceledTaskLog(Long taskId, Date date) {
        UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                .set("start_datetime", date)
                .set("update_time", new Date())
                .set("case_count", 0)
                .eq("patrol_task_id", taskId);
        update(updateWrapper);
    }

    @Override
    public void beginPatrolLog(PatrolTaskBeginRequest request) {
        UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                .set("start_datetime", new Date())
                .set("update_time", new Date())
                .set("start_place", request.getStartPlace())
                .eq("patrol_task_id", request.getTaskId());
        update(updateWrapper);
    }

    @Override
    public void finishPatrolLog(PatrolTaskFinishRequest request) {
        UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                .set("end_datetime", new Date())
                .set("submit_type", EPatrolLogSubmitType.Normal.getCode())
                .set("patrol_name", request.getPatrolName())
                .set("end_place", request.getEndPlace())
                .set("log_content", request.getLogContent())
                .set("status", EPatrolLogStatus.Committed.getCode())
                .set("update_time", new Date())
                .eq("id", request.getPatrolLogId());
        update(updateWrapper);

        if (CollectionUtils.isNotEmpty(request.getFileSaveRequestList())) {
            remoteFileService.addBatch("patrolLog", request.getPatrolLogId(), request.getFileSaveRequestList());
        }
    }

    @Override
    public void forceCommitPatrolLog(PatrolTask patrolTask) {
        double patrolLength = 0;
	    String patrolPath = null;
        String startPlace = "";
        String endPlace = "";
        PatrolPathSaveRequest startPath = new PatrolPathSaveRequest();
        PatrolPathSaveRequest endPath = new PatrolPathSaveRequest();
        Collection<String> taskPathKeys = redisService.mGet(EPatrolRedisKey.PatrolPath, patrolTask.getId().toString());
        if (CollectionUtils.isNotEmpty(taskPathKeys)) {
            List<PatrolPathSaveRequest> allPathList = taskPathKeys.stream().map(
                            t -> JSONObject.parseObject(t, PatrolPathSaveRequest.class)).
                    sorted(Comparator.comparing(PatrolPathSaveRequest::getPatrolDatetime)).collect(Collectors.toList());
            startPath = allPathList.get(0);
            endPath = allPathList.get(allPathList.size() - 1);
            Long patrolLogId = selectLogIdByTask(patrolTask.getId());
            List<Coordinate> coordinateList = allPathList.stream().map(t -> new Coordinate(t.parseLon(), t.parseLat())).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(coordinateList) || coordinateList.size() <= 1) {
                log.info("巡查日志轨迹无效：{}，{}", patrolLogId, JSONObject.toJSONString(coordinateList));
            }
            patrolPath = GeometryUtils.parseLineStringWtkStr(coordinateList);
            patrolLength = GeometryUtils.createLineString(patrolPath).getLength();
        }

        Result<AddressResult> addressStart = remoteAddressService.getAddressByLonLat(startPath.getLonLat());
        if (null != addressStart.getData()) {
            startPlace = addressStart.getData().getRegeocode().getFormatted_address();
        }
        Result<AddressResult> addressEnd = remoteAddressService.getAddressByLonLat(endPath.getLonLat());
        if (null != addressEnd.getData()) {
            endPlace = addressEnd.getData().getRegeocode().getFormatted_address();
        }

        UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                .set("submit_type", EPatrolLogSubmitType.System.getCode())
                .set("patrol_name", DateUtil.format(new Date(), CHINESE_DATE_PATTERN) + "系统提交")
                .set("log_content", DateUtil.format(new Date(), CHINESE_DATE_PATTERN) + "系统提交")
                .set("status", EPatrolLogStatus.Committed.getCode())
                .set("start_datetime", DateUtil.format(startPath.getPatrolDatetime(), NORM_DATETIME_PATTERN))
                .set("end_datetime", new Date())
                .set("update_time", new Date())
	            .set(StringUtils.isNotBlank(patrolPath), "patrol_path", patrolPath)
                .set("patrol_length", patrolLength)
                .set("start_place", startPlace)
                .set("end_place", endPlace)
                .eq("patrol_task_id", patrolTask.getId());
        update(updateWrapper);
    }


    @Override
    public void stopPatrolLogBySystem(Long taskId) {
        UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                .set("submit_type", EPatrolLogSubmitType.System.getCode())
                .set("patrol_name", DateUtil.format(new Date(), CHINESE_DATE_PATTERN) + "系统提交")
                .set("log_content", DateUtil.format(new Date(), CHINESE_DATE_PATTERN) + "系统提交")
                .set("status", EPatrolLogStatus.Committed.getCode())
                .set("end_datetime", new Date())
                .set("update_time", new Date())
                .eq("patrol_task_id", taskId);
        update(updateWrapper);

        // 提交巡查轨迹
        submitPatrolPath(taskId, BigDecimal.ZERO);
    }

    @Override
    public String submitPatrolPath(Long taskId, BigDecimal patrolLength) {
        Long patrolLogId = selectLogIdByTask(taskId);
        if (null == patrolLogId) {
            return "";
        }

        String patrolPath = parseRedisPatrolPath(patrolLogId);
        UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                .setSql(StringUtils.isNotBlank(patrolPath), String.format(" patrol_path = geomfromtext('%s') ", patrolPath))
                .set("patrol_length", patrolLength)
                .set("update_time", new Date())
                .eq("id", patrolLogId);
        update(updateWrapper);

        cleanPatrolPath(patrolLogId);

        return patrolPath;
    }

    @Override
    public boolean calcPatrolCoverRate(PatrolSection patrolSection, Long patrolTaskId, String patrolPath) {
        if (StringUtils.isBlank(patrolPath) || !EPatrolSectionType.Line.getCode().equals(patrolSection.getSectionType())) {
            return true;
        }

        // 巡查配置：缓冲区范围（长度） | 覆盖率（比例）
        double bufferScope = Double.parseDouble(ConfigUtils.getConfig(EPatrolConfig.BufferScope.getCode()));
        double coverRate = Double.parseDouble(ConfigUtils.getConfig(EPatrolConfig.CoverRate.getCode()));

        // 巡查段缓冲区和巡查轨迹缓冲区
        Polygon patrolSectionBuffer = GeometryUtils.createPolygon(patrolSection.getGeometryBuffer());
        LineString patrolPathLineString = GeometryUtils.createLineString(patrolPath);
        Geometry patrolPathBuffer = GeometryUtils.bufferLineString(patrolPathLineString, bufferScope);

        // 求交集，算比率
        if (null != patrolSectionBuffer && null != patrolPathBuffer) {
            Geometry mixedGeometry = patrolPathBuffer.intersection(patrolSectionBuffer);
            Double realCoverRate = mixedGeometry.getArea() / patrolSectionBuffer.getArea();

            UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                    .set("cover_rate", coverRate)
                    .set("real_cover_rate", realCoverRate)
                    .eq("id", selectLogIdByTask(patrolTaskId));
            update(updateWrapper);
        }

        return true;
    }

    @Override
    public boolean update(PatrolLogUpdateRequest request) {
        PatrolLog patrolLog = new PatrolLog();
        BeanUtils.copyProperties(request, patrolLog);
        return updateById(patrolLog);
    }

    @Override
    public boolean submitTemp(PatrolLogSubmitTempRequest request) {
        PatrolLog patrolLog = new PatrolLog();
        BeanUtils.copyProperties(request, patrolLog);
        // 巡查轨迹
        String patrolPath = parseRedisPatrolPath(request.getId());

        patrolLog.setSubmitType(EPatrolLogSubmitType.Normal.getCode());
        patrolLog.setStatus(EPatrolLogStatus.Committed.getCode());
        patrolLog.setEndDatetime(new Date());
        patrolLog.setPatrolPath(patrolPath);
        patrolLog.setPatrolLength(request.getPatrolLength());
        patrolLog.setEndPlace(request.getEndPlace());
        updateById(patrolLog);

        if (CollectionUtils.isNotEmpty(request.getFileSaveRequestList())) {
            remoteFileService.addBatch("patrolLog", request.getId(), request.getFileSaveRequestList());
        }

        // 删除今日巡查
        redisService.deleteObject(EPatrolRedisKey.TodayPatrolTask, request.getId().toString());

        return false;
    }

    @Override
    public boolean cancelTemp(Long patrolLogId) {
        // 删除轨迹
        cleanPatrolPath(patrolLogId);

        // 删除案件
        redisService.deletePatternObject(EPatrolRedisKey.PatrolCase, patrolLogId.toString());

        // 删除打卡
        patrolPunchService.deleteByLogId(patrolLogId);

        return delete(patrolLogId.toString());
    }

    @Override
    public boolean updateCaseCount(Long patrolLogId, String operation) {
        if (null == patrolLogId) {
            return true;
        }

        UpdateWrapper<PatrolLog> updateWrapper = new UpdateWrapper<PatrolLog>()
                .setSql("case_count = case_count " + operation + " 1")
                .eq("id", patrolLogId);
        return update(updateWrapper);
    }

    private void cleanPatrolPath(Long patrolLogId) {
        redisService.deletePatternObject(EPatrolRedisKey.PatrolPath, patrolLogId.toString());
    }

    @Override
    public Long selectLogIdByTask(Long taskId) {
        QueryWrapper<PatrolLog> queryWrapper = new QueryWrapper<PatrolLog>().eq("patrol_task_id", taskId);
        PatrolLog patrolLog = getOne(queryWrapper);
        return null != patrolLog ? patrolLog.getId() : null;
    }

    @Override
    public PatrolLog selectByTask(Long taskId) {
        QueryWrapper<PatrolLog> queryWrapper = new QueryWrapper<PatrolLog>().eq("patrol_task_id", taskId);
        return getOne(queryWrapper);
    }

    @Override
    public String parseRedisPatrolPath(Long patrolLogId) {
        String patrolPath = null;
        Collection<String> patrolPathStringList = redisService.mGet(EPatrolRedisKey.PatrolPath, patrolLogId.toString());
        if (CollectionUtils.isNotEmpty(patrolPathStringList)) {
            // 全部巡查轨迹
            List<PatrolPathSaveRequest> allPathList = patrolPathStringList.stream().map(
                            t -> JSONObject.parseObject(t, PatrolPathSaveRequest.class)).
                    sorted(Comparator.comparing(PatrolPathSaveRequest::getPatrolDatetime)).collect(Collectors.toList());

            List<Coordinate> coordinateList = allPathList.stream().map(t -> new Coordinate(t.parseLon(), t.parseLat())).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(coordinateList) || coordinateList.size() <= 1) {
                log.info("巡查日志轨迹无效：{}，{}", patrolLogId, JSONObject.toJSONString(coordinateList));
            }
            patrolPath = GeometryUtils.parseLineStringWtkStr(coordinateList);
        }
        return patrolPath;
    }

    @Override
    public String getPatrolPath(Long patrolLogId) {
        PatrolLog patrolLog = patrolLogMapper.selectById(patrolLogId);
        if (EPatrolLogStatus.Committed.getCode().equals(patrolLog.getStatus())) {
            return patrolLog.getPatrolPath();
        } else {
            return parseRedisPatrolPath(patrolLogId);
        }
    }

    @Override
    public PatrolLog selectDetail(Long id) {
        PatrolLog patrolLog = patrolLogMapper.selectOneById(id);
        if (null == patrolLog) {
            throw new ServiceException(String.format("%s ID=%s 的记录不存在", this.getClass().getSimpleName(), id));
        }
        wrapper(patrolLog);
        return patrolLog;
    }

    @Override
    public PatrolLog selectDetailBrief(Long id) {
        return patrolLogMapper.selectById(id);
    }

    @Override
    public PatrolLog selectTempByUserId(Long userId) {
        QueryWrapper<PatrolLog> queryWrapper = new QueryWrapper<PatrolLog>().
                eq("log_user_id", userId).
                eq("status", EPatrolLogStatus.Temp.getCode()).last(" limit 1 ");
        return getOne(queryWrapper);
    }

    @Override
    public List<PatrolLog> selectPage(PatrolLogQueryRequest request) {
        return patrolLogMapper.selectByCondition(request);
    }

    @Override
    public List<PatrolLog> selectList(PatrolLogQueryRequest request) {
        return patrolLogMapper.selectByCondition(request);
    }

    @Override
    public List<PatrolLog> selectRealtimeList(PatrolLogQueryRequest request) {
        Collection<String> patrolLogKeys = redisService.keys(EPatrolRedisKey.TodayPatrolTask);
        if (CollectionUtils.isEmpty(patrolLogKeys)) {
            return Collections.emptyList();
        }
        List<Long> patrolLogIdList = patrolLogKeys.stream().map(t -> Long.parseLong(t.split(":")[1])).distinct().collect(Collectors.toList());
        request.setIdList(patrolLogIdList);
        List<PatrolLog> patrolLogList = patrolLogMapper.selectByCondition(request);
        for (PatrolLog patrolLog : patrolLogList) {
            patrolLog.setPatrolPath(parseRedisPatrolPath(patrolLog.getId()));
        }
        return patrolLogList;
    }

    private void wrapper(PatrolLog patrolLog) {
        if (StringUtils.isBlank(patrolLog.getPatrolPath())) {
            patrolLog.setPatrolPath(parseRedisPatrolPath(patrolLog.getId()));
        }
        if (StringUtils.isNotBlank(patrolLog.getPatrolPath())) {
            patrolLog.setPatrolPathLngLat(GeometryUtils.parseLineStringLngLat(patrolLog.getPatrolPath()));
        }
    }

    @Override
    public void export(HttpServletResponse response, PatrolLogQueryRequest request) {
        SimpleDateFormat sdf = new SimpleDateFormat(NORM_DATETIME_MINUTE_PATTERN);
        List<PatrolLog> patrolLogs = patrolLogMapper.selectByCondition(request);
        if (CollectionUtils.isNotEmpty(patrolLogs)) {
            // 目前只支持单个日志导出，不支持多个日志批量导出
            PatrolLog patrolLog = patrolLogs.get(0);
            PatrolLogExportRequest exportRequest = new PatrolLogExportRequest();
            exportRequest.setTitle(patrolLog.getPatrolName());
            exportRequest.setSectionName(patrolLog.getPatrolSectionName());
            exportRequest.setPatrolName(patrolLog.getPatrolName());
            exportRequest.setPatrolPersonName(patrolLog.getTaskUserName());
            exportRequest.setPatrolTime(sdf.format(patrolLog.getStartDatetime()) + "--" + sdf.format(patrolLog.getEndDatetime()));
            exportRequest.setPatrolLocation(patrolLog.getStartPlace() + "-" + patrolLog.getEndPlace());
            BigDecimal patrolLength = patrolLog.getPatrolLength();
            exportRequest.setPatrolLength((patrolLength != null ? String.valueOf(patrolLength) : 0) + "km");
            exportRequest.setPatrolDescription(patrolLog.getLogContent());
            exportRequest.setPatrolStatus(EPatrolLogSubmitType.match(patrolLog.getSubmitType()).getValue());

            PatrolCaseQueryRequest patrolCaseQueryRequest = new PatrolCaseQueryRequest();
            patrolCaseQueryRequest.setPatrolLogId(patrolLog.getId());
            List<PatrolCase> patrolCaseList = patrolCaseMapper.selectByCondition(patrolCaseQueryRequest);
            AtomicInteger i = new AtomicInteger(1);

            // 巡查问题数据
            List<List<String>> patrolCaseData = new ArrayList<>();
            patrolCaseList.forEach(patrolCase -> {
                List<String> rowDataList = new ArrayList<>();
                rowDataList.add(String.valueOf(i.getAndIncrement()));
                rowDataList.add(patrolCase.getCaseTypeStr());
                rowDataList.add(patrolCase.getCaseAddress());
                rowDataList.add(patrolCase.getCaseContent());
                rowDataList.add(patrolCase.getStatusStr());
                patrolCaseData.add(rowDataList);
            });

            exportRequest.setTableData(patrolCaseData);

            // 巡查图片
            List<Map> photoList = new ArrayList<>();
            List<SysFile> baseFileInfoEntityList = remoteFileService.list(patrolLog.getId()).getData();
            if (CollectionUtils.isNotEmpty(baseFileInfoEntityList)) {
                baseFileInfoEntityList.forEach(pictureUrl -> {
                    Map<String, String> photoMap = new HashMap();
                    photoMap.put("width", "200");
                    photoMap.put("height", "200");
                    photoMap.put("imgPath", pictureUrl.getUrl());
                    photoList.add(photoMap);
                });
            }
            exportRequest.setPhotos(photoList);
            try {
                WordBaseService wordBaseService = new WordBaseService();
                byte[] bytes = wordBaseService.toWordList(exportRequest, PATROL_LOG_EXPORT_TEMPLATE);
                if (bytes != null) {
                    OfficeUtil.exportByteStreamToClient(response, bytes, PATROL_LOG_EXPORT_FILE_NAME);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}
